<template>
  <el-dialog :model-value="modelValue" @close="close()" :fullscreen="fullscreen">
    <template #header="{ close, titleId, titleClass }">
      <div class="header" @click="fullScreenClick()">
        <svg-icon :icon-class="fullscreen ? 'exit-fullscreen' : 'fullscreen'" />
      </div>
    </template>
    <div ref="el" class="workflow-canvas">
      <svg ref="svg" style="width:100%;min-height: 600px;">
        <g ref="inner"></g>
      </svg>
    </div>
  </el-dialog>
</template>

<script setup lang="ts">
import FlowChartApi from '@/api/workflow/flowChart';
import { ActivityModel, WorkflowModel } from '@/api/workflow/flowChart/view';
import { getChildActivities } from '@/api/workflow/flowChart/utils';
import * as d3 from 'd3';
import dagreD3 from 'dagre-d3';
const emits = defineEmits(["update:modelValue"]);
const props = defineProps({
  modelValue: Boolean,
  workflowInstanceId: String,
});
const close = () => {
  emits("update:modelValue");
};

const fullscreen = ref(false);
const fullScreenClick = () => { fullscreen.value = !fullscreen.value; svg.value.style.height = "calc(100vh - 64px)"; };

const el = ref<HTMLElement>(null);
const svg = ref<SVGSVGElement>(null);
const inner = ref<SVGGElement>(null);
let svgD3Selected: d3.Selection<SVGSVGElement, unknown, null, undefined>;
let innerD3Selected: d3.Selection<SVGGElement, unknown, null, undefined>;
let zoomParams: { x: number; y: number; scale: number, initialZoom: boolean } = { x: 0, y: 0, scale: 1, initialZoom: true };
const dagreD3Renderer: dagreD3.Render = new dagreD3.render();
let zoom: d3.ZoomBehavior<Element, unknown>;
let graph: dagreD3.graphlib.Graph;
let workflowModel: WorkflowModel;

onMounted(async () => {
  workflowModel = await FlowChartApi.queryModels(props.workflowInstanceId);
  svgD3Selected = d3.select(svg.value);
  innerD3Selected = d3.select(inner.value);

  safeRender();
})

const safeRender = () => {
  // Rebuild D3 model if component completed its initial load.
  if (!svgD3Selected)
    return;

  const rect = el.value.getBoundingClientRect();
  if (rect.height === 0 || rect.width === 0) {
    const observer = new ResizeObserver(entries => {
      for (let entry of entries) {
        const rect = entry.contentRect;
        if (rect.height > 0 && rect.width > 0) {
          renderTree();
          observer.unobserve(entry.target);
        }
      }
    });

    observer.observe(el.value);
  } else {
    renderTree();
  }
}

const renderTree = () => {
  applyZoom();
  setEntities();
  renderNodes();
}

const applyZoom = () => {
  zoom = d3.zoom().on('zoom', event => {
    const { transform } = event;
    innerD3Selected.attr('transform', transform);
    zoomParams = {
      x: transform.x,
      y: transform.y,
      scale: transform.k,
      initialZoom: zoomParams.initialZoom,
    };

    //fix for safari
    const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
    if (isSafari) {
      d3.selectAll('g.label > * > * > div, g.label > * > div').style('transform', `scale(${transform.k})`);
    }
  });
  svgD3Selected.call(zoom);
}

const setEntities = () => {
  // 配置查看https://github.com/dagrejs/dagre/wiki
  graph = new dagreD3.graphlib.Graph().setGraph({
    nodesep: 50,
    // ranksep: 30,
    rankdir: 'TB'
  });

  const rootActivities = getRootActivities();
  //绘制节点
  graph.setNode('start', {
    label: `<button class="el-button el-button--success el-button--default">Start</button>`,
    labelType: 'html',
    class: 'start',
    style: "fill:none",
  });

  // Connections between Start and root activities.
  rootActivities.forEach(activity => {
    graph.setEdge('start', `${activity.activityId}`, {
      arrowhead: "vee",
      arrowheadStyle: "fill: #f66",
      style: "stroke: #f66;stroke-width: 1.5px;stroke-dasharray: 5, 5;fill: none;"
    });
  });

  // Connections between activities and their outcomes.
  const activityDisplay = workflowModel.activityDisplayContexts || {};

  workflowModel.activities.forEach(activity => {

    const execlog = workflowModel.workflowInstance.activityData[activity.activityId] || {};
    graph.setNode(activity.activityId, createActivityOptions(activity, execlog));

    const displayContext = activityDisplay[activity.activityId] || undefined;
    const outcomes = !!displayContext ? displayContext.outcomes : activity.outcomes || [];

    outcomes.forEach(outcome => {
      graph.setNode(`${activity.activityId}/${outcome}`, {
        shape: 'rect',
        outcome,
        activity,
        label: `${outcome}`,
        style: "fill:none",
        labelType: 'html',
        class: 'add'
      });
      graph.setEdge(activity.activityId, `${activity.activityId}/${outcome}`, {
        arrowhead: "undirected",
        arrowheadStyle: "fill: #f66",
        style: "stroke: #f66;stroke-width: 1.5px;stroke-dasharray: 5, 5;fill: none;"
      });
    });
  });

  workflowModel.connections.forEach(({ sourceId, targetId, outcome }) => {
    const sourceName = `${sourceId}/${outcome}`;

    if (!graph.hasNode(sourceName)) {
      console.warn(`No source node with ID '${sourceName}' exists.`);
      return;
    }

    graph.setEdge(sourceName, targetId, {
      arrowhead: "vee",
      arrowheadStyle: "fill: #f66",
      style: "stroke: #f66;stroke-width: 1.5px;stroke-dasharray: 5, 5;fill: none;"
    });
  });
}

const renderNodes = () => {
  const prevTransform = innerD3Selected.attr('transform');
  const scaleAfter = zoomParams.scale;
  svgD3Selected.call(zoom.scaleTo, 1);
  dagreD3Renderer(innerD3Selected as any, graph as any);
  svgD3Selected.call(zoom.scaleTo, scaleAfter);
  innerD3Selected.attr('transform', prevTransform);
  if (zoomParams.initialZoom === true) {
    applyInitialZoom();
  }
}


const applyInitialZoom = () => {
  const { width: widthSvg }: { width: number } = svgD3Selected.node().getBBox();
  const middleScreen: number = svgD3Selected.node().clientWidth / 2;
  const nodeStartTransform: string = d3.select('.start').attr('transform');
  const nodeStartTranslateX: number = parseInt(nodeStartTransform.replace(/translate|((\)|\())/g, '').split(',')[0]);

  const zoomParamsScale: number = 1;
  const zoomParamsY: number = 50;
  const zoomParamsX: number = middleScreen - (widthSvg - (widthSvg - nodeStartTranslateX));

  zoom.scaleTo(svgD3Selected, zoomParamsScale);
  zoom.translateTo(svgD3Selected, zoomParamsX, zoomParamsY);

  svgD3Selected.call(zoom.transform, d3.zoomIdentity.scale(zoomParamsScale).translate(zoomParamsX, zoomParamsY));

  zoomParams.initialZoom = false;
}

const getRootActivities = () => {
  return getChildActivities(workflowModel, null);
}

const createActivityOptions = (activity: ActivityModel, activityData) => {
  const lifecycle = activityData['_Lifecycle'] || {};
  const executing = lifecycle.executing ?? lifecycle.Executing;
  const executed = lifecycle.executed ?? lifecycle.Executed;
  let color = "primary";
  if (executing) {
    color = 'warning';
  }
  if (executed) {
    color = 'success';
  }
  return {
    shape: 'rect',
    label: `<button class="el-button el-button--${color} el-button--default">${activity.displayName}</button>`,
    style: "fill:none",
    rx: 5,
    ry: 5,
    labelType: 'html',
    class: 'activity',
    activity,
  };
}

</script>

<style lang="scss" scoped>
.header {
  position: absolute;
  right: 48px;
  top: 0;
  width: 48px;
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.workflow-canvas {
  background-image: url('');
  background-repeat: repeat;
  background-size: 30px 30px;
  background-color: #fbfbfb;
}
</style>
